1 /*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.google.common.util.concurrent;
18
19 import static com.google.common.base.Preconditions.checkNotNull;
20
21 import com.google.common.annotations.GwtCompatible;
22 import com.google.common.base.Function;
23 import com.google.common.collect.Maps;
24
25 import java.util.Collections;
26 import java.util.Map;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.atomic.AtomicLong;
29
30 /**
31 * A map containing {@code long} values that can be atomically updated. While writes to a
32 * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map
33 * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with
34 * {@code K}. If a key has not yet been associated with a value, its implicit value is zero.
35 *
36 * <p>Most methods in this class treat absent values and zero values identically, as individually
37 * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty},
38 * {@link #asMap}, and {@link #toString}.
39 *
40 * <p>Instances of this class may be used by multiple threads concurrently. All operations are
41 * atomic unless otherwise noted.
42 *
43 * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a
44 * {@link com.google.common.collect.Multiset} such as
45 * {@link com.google.common.collect.ConcurrentHashMultiset} instead.
46 *
47 * <b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically
48 * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}.
49 *
50 * @author Charles Fry
51 * @since 11.0
52 */
53 @GwtCompatible
54 public final class AtomicLongMap<K> {
55 private final ConcurrentHashMap<K, AtomicLong> map;
56
57 private AtomicLongMap(ConcurrentHashMap<K, AtomicLong> map) {
58 this.map = checkNotNull(map);
59 }
60
61 /**
62 * Creates an {@code AtomicLongMap}.
63 */
64 public static <K> AtomicLongMap<K> create() {
65 return new AtomicLongMap<K>(new ConcurrentHashMap<K, AtomicLong>());
66 }
67
68 /**
69 * Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}.
70 */
71 public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) {
72 AtomicLongMap<K> result = create();
73 result.putAll(m);
74 return result;
75 }
76
77 /**
78 * Returns the value associated with {@code key}, or zero if there is no value associated with
79 * {@code key}.
80 */
81 public long get(K key) {
82 AtomicLong atomic = map.get(key);
83 return atomic == null ? 0L : atomic.get();
84 }
85
86 /**
87 * Increments by one the value currently associated with {@code key}, and returns the new value.
88 */
89 public long incrementAndGet(K key) {
90 return addAndGet(key, 1);
91 }
92
93 /**
94 * Decrements by one the value currently associated with {@code key}, and returns the new value.
95 */
96 public long decrementAndGet(K key) {
97 return addAndGet(key, -1);
98 }
99
100 /**
101 * Adds {@code delta} to the value currently associated with {@code key}, and returns the new
102 * value.
103 */
104 public long addAndGet(K key, long delta) {
105 outer: for (;;) {
106 AtomicLong atomic = map.get(key);
107 if (atomic == null) {
108 atomic = map.putIfAbsent(key, new AtomicLong(delta));
109 if (atomic == null) {
110 return delta;
111 }
112 // atomic is now non-null; fall through
113 }
114
115 for (;;) {
116 long oldValue = atomic.get();
117 if (oldValue == 0L) {
118 // don't compareAndSet a zero
119 if (map.replace(key, atomic, new AtomicLong(delta))) {
120 return delta;
121 }
122 // atomic replaced
123 continue outer;
124 }
125
126 long newValue = oldValue + delta;
127 if (atomic.compareAndSet(oldValue, newValue)) {
128 return newValue;
129 }
130 // value changed
131 }
132 }
133 }
134
135 /**
136 * Increments by one the value currently associated with {@code key}, and returns the old value.
137 */
138 public long getAndIncrement(K key) {
139 return getAndAdd(key, 1);
140 }
141
142 /**
143 * Decrements by one the value currently associated with {@code key}, and returns the old value.
144 */
145 public long getAndDecrement(K key) {
146 return getAndAdd(key, -1);
147 }
148
149 /**
150 * Adds {@code delta} to the value currently associated with {@code key}, and returns the old
151 * value.
152 */
153 public long getAndAdd(K key, long delta) {
154 outer: for (;;) {
155 AtomicLong atomic = map.get(key);
156 if (atomic == null) {
157 atomic = map.putIfAbsent(key, new AtomicLong(delta));
158 if (atomic == null) {
159 return 0L;
160 }
161 // atomic is now non-null; fall through
162 }
163
164 for (;;) {
165 long oldValue = atomic.get();
166 if (oldValue == 0L) {
167 // don't compareAndSet a zero
168 if (map.replace(key, atomic, new AtomicLong(delta))) {
169 return 0L;
170 }
171 // atomic replaced
172 continue outer;
173 }
174
175 long newValue = oldValue + delta;
176 if (atomic.compareAndSet(oldValue, newValue)) {
177 return oldValue;
178 }
179 // value changed
180 }
181 }
182 }
183
184 /**
185 * Associates {@code newValue} with {@code key} in this map, and returns the value previously
186 * associated with {@code key}, or zero if there was no such value.
187 */
188 public long put(K key, long newValue) {
189 outer: for (;;) {
190 AtomicLong atomic = map.get(key);
191 if (atomic == null) {
192 atomic = map.putIfAbsent(key, new AtomicLong(newValue));
193 if (atomic == null) {
194 return 0L;
195 }
196 // atomic is now non-null; fall through
197 }
198
199 for (;;) {
200 long oldValue = atomic.get();
201 if (oldValue == 0L) {
202 // don't compareAndSet a zero
203 if (map.replace(key, atomic, new AtomicLong(newValue))) {
204 return 0L;
205 }
206 // atomic replaced
207 continue outer;
208 }
209
210 if (atomic.compareAndSet(oldValue, newValue)) {
211 return oldValue;
212 }
213 // value changed
214 }
215 }
216 }
217
218 /**
219 * Copies all of the mappings from the specified map to this map. The effect of this call is
220 * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key
221 * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined
222 * if the specified map is modified while the operation is in progress.
223 */
224 public void putAll(Map<? extends K, ? extends Long> m) {
225 for (Map.Entry<? extends K, ? extends Long> entry : m.entrySet()) {
226 put(entry.getKey(), entry.getValue());
227 }
228 }
229
230 /**
231 * Removes and returns the value associated with {@code key}. If {@code key} is not
232 * in the map, this method has no effect and returns zero.
233 */
234 public long remove(K key) {
235 AtomicLong atomic = map.get(key);
236 if (atomic == null) {
237 return 0L;
238 }
239
240 for (;;) {
241 long oldValue = atomic.get();
242 if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
243 // only remove after setting to zero, to avoid concurrent updates
244 map.remove(key, atomic);
245 // succeed even if the remove fails, since the value was already adjusted
246 return oldValue;
247 }
248 }
249 }
250
251 /**
252 * Removes all mappings from this map whose values are zero.
253 *
254 * <p>This method is not atomic: the map may be visible in intermediate states, where some
255 * of the zero values have been removed and others have not.
256 */
257 public void removeAllZeros() {
258 for (K key : map.keySet()) {
259 AtomicLong atomic = map.get(key);
260 if (atomic != null && atomic.get() == 0L) {
261 map.remove(key, atomic);
262 }
263 }
264 }
265
266 /**
267 * Returns the sum of all values in this map.
268 *
269 * <p>This method is not atomic: the sum may or may not include other concurrent operations.
270 */
271 public long sum() {
272 long sum = 0L;
273 for (AtomicLong value : map.values()) {
274 sum = sum + value.get();
275 }
276 return sum;
277 }
278
279 private transient Map<K, Long> asMap;
280
281 /**
282 * Returns a live, read-only view of the map backing this {@code AtomicLongMap}.
283 */
284 public Map<K, Long> asMap() {
285 Map<K, Long> result = asMap;
286 return (result == null) ? asMap = createAsMap() : result;
287 }
288
289 private Map<K, Long> createAsMap() {
290 return Collections.unmodifiableMap(
291 Maps.transformValues(map, new Function<AtomicLong, Long>() {
292 @Override
293 public Long apply(AtomicLong atomic) {
294 return atomic.get();
295 }
296 }));
297 }
298
299 /**
300 * Returns true if this map contains a mapping for the specified key.
301 */
302 public boolean containsKey(Object key) {
303 return map.containsKey(key);
304 }
305
306 /**
307 * Returns the number of key-value mappings in this map. If the map contains more than
308 * {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.
309 */
310 public int size() {
311 return map.size();
312 }
313
314 /**
315 * Returns {@code true} if this map contains no key-value mappings.
316 */
317 public boolean isEmpty() {
318 return map.isEmpty();
319 }
320
321 /**
322 * Removes all of the mappings from this map. The map will be empty after this call returns.
323 *
324 * <p>This method is not atomic: the map may not be empty after returning if there were concurrent
325 * writes.
326 */
327 public void clear() {
328 map.clear();
329 }
330
331 @Override
332 public String toString() {
333 return map.toString();
334 }
335
336 /*
337 * ConcurrentMap operations which we may eventually add.
338 *
339 * The problem with these is that remove(K, long) has to be done in two phases by definition ---
340 * first decrementing to zero, and then removing. putIfAbsent or replace could observe the
341 * intermediate zero-state. Ways we could deal with this are:
342 *
343 * - Don't define any of the ConcurrentMap operations. This is the current state of affairs.
344 *
345 * - Define putIfAbsent and replace as treating zero and absent identically (as currently
346 * implemented below). This is a bit surprising with putIfAbsent, which really becomes
347 * putIfZero.
348 *
349 * - Allow putIfAbsent and replace to distinguish between zero and absent, but don't implement
350 * remove(K, long). Without any two-phase operations it becomes feasible for all remaining
351 * operations to distinguish between zero and absent. If we do this, then perhaps we should add
352 * replace(key, long).
353 *
354 * - Introduce a special-value private static final AtomicLong that would have the meaning of
355 * removal-in-progress, and rework all operations to properly distinguish between zero and
356 * absent.
357 */
358
359 /**
360 * If {@code key} is not already associated with a value or if {@code key} is associated with
361 * zero, associate it with {@code newValue}. Returns the previous value associated with
362 * {@code key}, or zero if there was no mapping for {@code key}.
363 */
364 long putIfAbsent(K key, long newValue) {
365 for (;;) {
366 AtomicLong atomic = map.get(key);
367 if (atomic == null) {
368 atomic = map.putIfAbsent(key, new AtomicLong(newValue));
369 if (atomic == null) {
370 return 0L;
371 }
372 // atomic is now non-null; fall through
373 }
374
375 long oldValue = atomic.get();
376 if (oldValue == 0L) {
377 // don't compareAndSet a zero
378 if (map.replace(key, atomic, new AtomicLong(newValue))) {
379 return 0L;
380 }
381 // atomic replaced
382 continue;
383 }
384
385 return oldValue;
386 }
387 }
388
389 /**
390 * If {@code (key, expectedOldValue)} is currently in the map, this method replaces
391 * {@code expectedOldValue} with {@code newValue} and returns true; otherwise, this method
392 * returns false.
393 *
394 * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)}
395 * is currently in the map, or if {@code key} is not in the map at all.
396 */
397 boolean replace(K key, long expectedOldValue, long newValue) {
398 if (expectedOldValue == 0L) {
399 return putIfAbsent(key, newValue) == 0L;
400 } else {
401 AtomicLong atomic = map.get(key);
402 return (atomic == null) ? false : atomic.compareAndSet(expectedOldValue, newValue);
403 }
404 }
405
406 /**
407 * If {@code (key, value)} is currently in the map, this method removes it and returns
408 * true; otherwise, this method returns false.
409 */
410 boolean remove(K key, long value) {
411 AtomicLong atomic = map.get(key);
412 if (atomic == null) {
413 return false;
414 }
415
416 long oldValue = atomic.get();
417 if (oldValue != value) {
418 return false;
419 }
420
421 if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
422 // only remove after setting to zero, to avoid concurrent updates
423 map.remove(key, atomic);
424 // succeed even if the remove fails, since the value was already adjusted
425 return true;
426 }
427
428 // value changed
429 return false;
430 }
431
432 }